package com.blank.shop.ssr;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * redis 作者 blog ： http://antirez.com/
 * <p>
 * 中文部分参考命令：http://doc.redisfans.com/
 * <p>
 * spring mvc + spring + redis
 * <p>
 * EnableAsync : org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
 * <p>
 * 哈哈，有人编译了window版本的redis https://www.renren.io/detail/14294
 *
 * @author _blank
 */
@Slf4j
@EnableScheduling
@EnableAsync(proxyTargetClass = true)
@SpringBootApplication
public class SsrApplication {

    /*
     * redis api 操作参考这里 @com.blank.shop.ssr.runner.RedisRunnerBean
     *
     * redis string 三大编码格式：
     *     int (8个字节)： 就是java java.lang.Long类型的 min(-9223372036854775808) 到 max(9223372036854775807) 范围
     *     embstr : 代表 embstr 格式的 SDS(Simple Dynamic String 简单动态字符串),保存长度小于44字节的字符串
     *     raw : 保存长度大于44字节的字符串 (对embstr的字符串做修改都是会转成raw的，无论是否达到44字节)
     *
     *
     * redis hash 两种编码格式：
     *     ziplist : 压缩列表
     *     hashtable : 字典（dictionary）是数组+链表的结构
     *
     *
     * redis list 一种编码格式：
     *     quicklist : list用quicklist来存储，quicklist存储了一个双向链表，每个节点都是一个ziplist，是ziplist和linkedlist的结合体
     *
     *
     * redis set 两种编码格式：
     *     intset : 如果元素都是整数类型，就用intset存储。
     *     hashtable : 如果不是整数类型，就用hashtable（数组+链表的存来储结构）。
     *         key就是元素的值，value为null。
     *
     *
     * redis zset 两种编码格式：
     *     skiplist : 当有序集合中包含的元素数量超过服务器属性 server.zset_max_ziplist_entries 的值（默认值为 128 ），
                     或者有序集合中新添加元素的 member 的长度大于服务器属性 server.zset_max_ziplist_value 的值（默认值为 64 ）时，
                     redis会使用跳跃表作为有序集合的底层实现。
     *     ziplist : 否则会使用ziplist作为有序集合的底层实现
     *
     *
     *
     * Redis利用epoll来实现IO多路复用，将连接信息和事件放到队列中，一次放到文件事件分派器，事件分派器将事件分发给事件处理器。
     * Redis 是跑在单线程中的，所有的操作都是按照顺序线性执行的，但是由于读写操作等待用户输入或输出都是阻塞的，
     * 所以 I/O 操作在一般情况下往往不能直接返回，这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务，而 I/O 多路复用就是为了解决这个问题而出现。
     *
     * 所谓 I/O 多路复用机制，就是说通过一种机制，可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或写就绪），能够通知程序进行相应的读写操作。
     * 这种机制的使用需要 select 、 poll 、 epoll 来配合。
     * 多个连接共用一个阻塞对象，应用程序只需要在一个阻塞对象上等待，无需阻塞等待所有连接。
     * 当某条连接有新的数据可以处理时，操作系统通知应用程序，线程从阻塞状态返回，开始进行业务处理。
     *
     * Redis 服务采用 Reactor 的方式来实现文件事件处理器（每一个网络连接其实都对应一个文件描述符）
     * Redis基于Reactor模式开发了网络事件处理器，这个处理器被称为文件事件处理器。它的组成结构为4部分：
     *     多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
     * 因为文件事件分派器队列的消费是单线程的，所以Redis才叫单线程模型。
     *
     *
     *
     * I/O ：网络 I/O
     * 多路：多个客户端连接（连接就是套接字描述符，即 socket 或者 channel）
     * 复用：复用一个或几个线程。也就是说一个或一组线程处理多个 TCP 连接,使用单进程就能够实现同时处理多个客户端的连接
     *
     * select, poll, epoll 都是I/O多路复用的具体的实现
     * 参考文章：https://draveness.me/redis-io-multiplexing/
     * 因为 select 函数是作为 POSIX 标准中的系统调用，在不同版本的操作系统上都会实现，所以将其作为保底方案：
     * Redis 会优先选择时间复杂度为 O(1) 的 I/O 多路复用函数作为底层实现，
     * 包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOS/FreeBSD 中的 kqueue，
     * 上述的这些函数都使用了内核内部的结构，并且能够服务几十万的文件描述符。
     * 但是如果当前编译环境没有上述函数，就会选择 select 作为备选方案，由于其在使用时会扫描全部监听的描述符，
     * 所以其时间复杂度较差 O(n)，并且只能同时服务 1024 个文件描述符，所以一般并不会以 select 作为第一方案使用。
     *
     * epoll是现在最先进的IO多路复用器，Redis、Nginx，linux中的Java NIO都使用的是epoll。
     *
     *
     *
     * 参考 https://www.google.com/search?q=%E9%AB%98%E5%B9%B6%E5%8F%91+%E6%9B%B4%E6%96%B0%E7%BC%93%E5%AD%98+%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%93
     * 高并发场景下，到底先更新缓存还是先更新数据库？https://cloud.tencent.com/developer/article/1774867
     * 缓存更新的策略主要分为三种：
     *     Cache aside
     *         一：先更新数据库，再更新缓存
     *         二：先删除缓存，再更新数据库
     *         三：先更新数据库，再删除缓存
     *     Read/Write through
     *     Write behind
     *
     *
     *
     * 参考why技术： https://www.toutiao.com/a6991681691366474248/
     * 缓存雪崩：
     *     1、redis主机挂了，Redis 全盘崩溃
     *     2、比如缓存中有大量数据同时过期
     *   解决：
     *     1、redis集群实现高可用（sentinel 或者 cluster）
     *     2、ehcache本地缓存 + netflix hystrix/alibaba sentinel的服务限流&降级
     *     3、开启redis持久化机制，rdb/aof，尽快恢复redis集群
     *
     *
     * 缓存穿透：
     *     1、请求去查询一条记录，先redis后mysql发现都查询不到该条记录，但是请求每次都会打到数据库上面去，导致后台数据库压力暴增；
     *        简单说就是本来无一物，既不在Redis缓存中，也不在数据库中。
     *   解决：
     *     1、google guava bloomFilter 或者 redis集成module redis bloomFilter
     *
     *
     * 缓存击穿：
     *     1、大量的请求同时查询一个 key 时，此时这个key正好失效了，就会导致大量的请求都打到数据库上面去；
     *        简单说就是热点key突然失效了，暴打mysql
     *   解决：
     *     1、对于访问频繁的key，干脆不设置过期时间，或者效仿Redisson的watchdog，给热点key续命
     *     2、（这个就是互斥更新）互斥独占锁（setnx或者Redisso lock），锁住某Key，待某线程去db拿到数据到redis后，再让后面的线程访问redis走缓存
     *     3、随机退避、差异失效时间
     */
    /*----------------------------------------------------------------------------------------------------------------*/
    /*----------------------------------------------------------------------------------------------------------------*/
    /*----------------------------------------------------------------------------------------------------------------*/
    /*----------------------------------------------------------------------------------------------------------------*/
    /*----------------------------------------------------------------------------------------------------------------*/
    /*----------------------------------------------------------------------------------------------------------------*/

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SsrApplication.class, args);
    }

    /**
     * <!-- canal数据双写 -->
     * gav https://search.maven.org/artifact/com.alibaba.otter/canal.client/1.1.5/jar
     * gav https://search.maven.org/artifact/com.alibaba.otter/canal.protocol/1.1.5/jar
     */
    @SuppressWarnings("unused")
    private void canalForRedis() throws Exception {
//        final CanalConnector canalConnector = CanalConnectors.newSingleConnector(new InetSocketAddress("", 11111), "", "", "");
//
//        canalConnector.connect();
//        canalConnector.subscribe("database.table");
//        canalConnector.rollback();
//
//        final Message withoutAck = canalConnector.getWithoutAck(1024);
//        final List<CanalEntry.Entry> entries = withoutAck.getEntries();
//        if (CollectionUtils.isEmpty(entries)) {
//            TimeUnit.SECONDS.sleep(2L);
//        } else {
//            for (CanalEntry.Entry entry : entries) {
//                if (Objects.equals(entry.getEntryType(), CanalEntry.EntryType.TRANSACTIONBEGIN)
//                        || Objects.equals(entry.getEntryType(), CanalEntry.EntryType.TRANSACTIONEND)) {
//                    continue;
//                }
//
//                final CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
//                final CanalEntry.EventType eventType = rowChange.getEventType();
//                final CanalEntry.Header entryHeader = entry.getHeader();
//                System.out.printf("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s"
//                        , entryHeader.getLogfileName()
//                        , entryHeader.getLogfileOffset()
//                        , entryHeader.getSchemaName()
//                        , entryHeader.getTableName()
//                        , eventType);
//
//                final List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
//                for (CanalEntry.RowData rowData : rowDatasList) {
//                    if (Objects.equals(eventType, CanalEntry.EventType.INSERT)) {
//                        final List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
//                        for (CanalEntry.Column column : afterColumnsList) {
//                        }
//                    } else if (Objects.equals(eventType, CanalEntry.EventType.DELETE)) {
//                        final List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
//                    } else if (Objects.equals(eventType, CanalEntry.EventType.UPDATE)) {
//                        final List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
//                    }
//                }
//            }
//        }
    }

    @SuppressWarnings("unused")
    private static void cpuAllocationWork() {
        final Thread.UncaughtExceptionHandler handler = (thread, ex) -> System.out.println("设置未捕获的异常信息输出！" + thread.getName() + "    " + ex.getMessage());
        Thread.currentThread().setUncaughtExceptionHandler(handler);

        // Java如何绑定线程到指定CPU上执行？ https://www.toutiao.com/a6976830153724985886/
        // https://github.com/OpenHFT/Java-Thread-Affinity
        // https://search.maven.org/artifact/net.openhft/affinity/3.2.3/bundle
//        try (AffinityLock affinityLock = AffinityLock.acquireLock(3)) {
//            while (true);
//        }
    }

    @SuppressWarnings("unused")
    private static void studyThreadPoolSourceCode() throws Exception {
        final Runnable task = () -> {
            try {
                TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
            System.out.println(Thread.currentThread() + "-执行任务");
        };

        final ThreadPoolExecutor executor = new ThreadPoolExecutor(2
                , 3
                , 10L
                , TimeUnit.SECONDS
                , new LinkedBlockingQueue<>(2)
                , Executors.defaultThreadFactory()
                , new ThreadPoolExecutor.AbortPolicy());

        executor.execute(task);
        executor.execute(task);
        executor.execute(task);
        executor.execute(task);
        executor.execute(task);

        while (true) {
            TimeUnit.SECONDS.sleep(30L);
            executor.execute(task);
        }
//        executor.shutdown();

//        System.out.println("break-points");
    }

}
